﻿// 2025/10/21: 首个版本

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Avalonia.Media;
using ASEva;
using ASEva.UIAvalonia;
using ASEva.Utility;
using Avalonia.Controls;

namespace CommonSource
{
    partial class TimestampsViewer : DialogPanel
    {
        public TimestampsViewer() // For designer
        {
            InitializeComponent();
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
        }

        public TimestampsViewer(String dataTitle, Timestamp[] timestamps, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(1100, 300, 1100, 600);
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = dataTitle + " " + language["timestamps"];

            var model = new TimestampsViewerViewModel(this, language);
            model.InitializeItems(timestamps);
            model.CloseRequest += delegate { Close(); };
            DataContext = model;
        }

        private LanguageSwitch language;
    }

    partial class TimestampsViewerViewModel(Control view, LanguageSwitch language) : ObservableObject
    {
        public async void InitializeItems(Timestamp[] timestamps)
        {
            if (timestamps == null || timestamps.Length == 0) return;

            var cpuModels = new Dictionary<SessionIdentifier, CPUTimeModel>();
            var hostModels = new Dictionary<SessionIdentifier, PosixTimeModel>();
            var gnssModels = new Dictionary<SessionIdentifier, PosixTimeModel>();

            foreach (var timestamp in timestamps)
            {
                if (cpuModels.ContainsKey(timestamp.Session)) continue;
                cpuModels[timestamp.Session] = await AgencyAsync.GetCPUTimeModel(timestamp.Session);
                hostModels[timestamp.Session] = await AgencyAsync.GetHostPosixTimeModel(timestamp.Session);
                gnssModels[timestamp.Session] = await AgencyAsync.GetGNSSPosixTimeModel(timestamp.Session);
            }

            var output = new List<TimestampItem>();
            for (int i = 0; i < timestamps.Length; i++)
            {
                var timestamp = timestamps[i];
                var item = new TimestampItem();
                output.Add(item);

                item.TimeOffset = (timestamp.Offset * 1000).ToString("F6");

                // sync state
                item.SyncState = "NONE";
                bool guestGreen = false, gnssGreen = false;
                if (timestamp.TimeInfo != null)
                {
                    switch (timestamp.OffsetSync)
                    {
                        case TimeOffsetSync.Server:
                            item.SyncState = "SERV";
                            guestGreen = true;
                            break;
                        case TimeOffsetSync.Gnss:
                            item.SyncState = "GNSS";
                            gnssGreen = true;
                            break;
                        case TimeOffsetSync.BusReceiverArrival:
                            item.SyncState = "RECV";
                            guestGreen = true;
                            break;
                        case TimeOffsetSync.Interpolated:
                            item.SyncState = "INTR";
                            break;
                    }
                }

                // cpu time
                ulong cpuTimeCalculated = 0, cpuTimeReal = 0;
                if (cpuModels.ContainsKey(timestamp.Session))
                {
                    var model = cpuModels[timestamp.Session];
                    if (model != null)
                    {
                        var cpuTimeSecCalculated = (double)model.StartCPUTick / model.CPUTicksPerSecond + timestamp.Offset;
                        cpuTimeCalculated = (ulong)(cpuTimeSecCalculated * 1000000000);
                        if (timestamp.TimeInfo != null && timestamp.TimeInfo.CPUTick != 0)
                        {
                            var cpuTimeSecReal = (double)timestamp.TimeInfo.CPUTick / model.CPUTicksPerSecond;
                            cpuTimeReal = (ulong)(cpuTimeSecReal * 1000000000);
                        }
                    }
                }
                if (cpuTimeCalculated == 0)
                {
                    item.CpuTime = "-";
                    item.CpuTimeColor = Brushes.Gray;

                }
                else if (cpuTimeReal == 0)
                {
                    item.CpuTime = posixToString(cpuTimeCalculated);
                    item.CpuTimeColor = Brushes.Gray;
                }
                else item.CpuTime = dualPosixToString(cpuTimeReal, cpuTimeCalculated);

                // host posix
                ulong hostPosixCalculated = 0, hostPosixReal = 0;
                if (hostModels.ContainsKey(timestamp.Session))
                {
                    var model = hostModels[timestamp.Session];
                    if (model != null) hostPosixCalculated = model.StartPosix * 1000000 + (ulong)(timestamp.Offset * model.TimeRatio * 1000000000);
                }
                if (timestamp.TimeInfo != null) hostPosixReal = timestamp.TimeInfo.HostPosix;
                if (hostPosixCalculated == 0)
                {
                    if (hostPosixReal == 0)
                    {
                        item.HostPosix = "-";
                        item.HostPosixColor = Brushes.Gray;
                    }
                    else item.HostPosix = posixToString(hostPosixReal);
                }
                else
                {
                    if (hostPosixReal == 0)
                    {
                        item.HostPosix = posixToString(hostPosixCalculated);
                        item.HostPosixColor = Brushes.Gray;
                    }
                    else item.HostPosix = dualPosixToString(hostPosixReal, hostPosixCalculated);
                }

                // guest posix
                ulong guestPosixReal = 0;
                if (timestamp.TimeInfo != null) guestPosixReal = timestamp.TimeInfo.GuestPosix;
                if (guestPosixReal == 0)
                {
                    item.GuestPosix = "-";
                    item.GuestPosixColor = Brushes.Gray;
                }
                else
                {
                    item.GuestPosix = posixToString(guestPosixReal);
                    if (guestGreen) item.GuestPosixColor = App.DarkTheme ? Brushes.LimeGreen : Brushes.Green;
                }

                // gnss posix
                ulong gnssPosixCalculated = 0, gnssPosixReal = 0;
                if (gnssModels.ContainsKey(timestamp.Session))
                {
                    var model = gnssModels[timestamp.Session];
                    if (model != null) gnssPosixCalculated = model.StartPosix * 1000000 + (ulong)(timestamp.Offset * model.TimeRatio * 1000000000);
                }
                if (timestamp.TimeInfo != null) gnssPosixReal = timestamp.TimeInfo.GNSSPosix;
                if (gnssPosixReal == 0)
                {
                    item.GnssPosix = "-";
                    item.GnssPosixColor = Brushes.Gray;
                }
                else
                {
                    if (gnssPosixCalculated == 0) item.GnssPosix = posixToString(gnssPosixReal);
                    else item.GnssPosix = dualPosixToString(gnssPosixReal, gnssPosixCalculated);
                    if (gnssGreen) item.GnssPosixColor = App.DarkTheme ? Brushes.LimeGreen : Brushes.Green;
                }
            }

            TimestampItems = output.ToArray();
        }

        private static String posixToString(ulong posixNano)
        {
            var posixMilli = posixNano / 1000000;
            var extraNano = posixNano % 1000000;
            return posixMilli + "." + extraNano.ToString("D6");
        }

        private static String dualPosixToString(ulong posixNanoReal, ulong posixNanoCalculated)
        {
            if (posixNanoReal >= posixNanoCalculated) return posixToString(posixNanoReal) + "(+" + posixToString(posixNanoReal - posixNanoCalculated) + ")";
            else return posixToString(posixNanoReal) + "(-" + posixToString(posixNanoCalculated - posixNanoReal) + ")";
        }

        public partial class TimestampItem : ObservableObject
        {
            public String TimeOffset { get; set; }
            public String SyncState { get; set; }
            public String CpuTime { get; set; }
            public IBrush CpuTimeColor { get; set; } = App.DarkTheme ? Brushes.LightGray : Brushes.Black;
            public String HostPosix { get; set; }
            public IBrush HostPosixColor { get; set; } = App.DarkTheme ? Brushes.LightGray : Brushes.Black;
            public String GuestPosix { get; set; }
            public IBrush GuestPosixColor { get; set; } = App.DarkTheme ? Brushes.LightGray : Brushes.Black;
            public String GnssPosix { get; set; }
            public IBrush GnssPosixColor { get; set; } = App.DarkTheme ? Brushes.LightGray : Brushes.Black;
        }

        [ObservableProperty]
        private TimestampItem[] timestampItems = [];

        [RelayCommand]
        private async Task Export()
        {
            var filePath = await App.ShowSaveFileDialog(view, null, null, null, language["suffix"], ".csv");
            if (filePath == null) return;

            using (var writer = new StreamWriter(filePath, false, Encoding.UTF8))
            {
                var firstRow = new List<String>();
                firstRow.Add(language["time-offset"]);
                firstRow.Add(language["sync-state"]);
                firstRow.Add(language["cpu-time"]);
                firstRow.Add(language["host-posix"]);
                firstRow.Add(language["guest-posix"]);
                firstRow.Add(language["gnss-posix"]);
                writer.WriteLine(String.Join(',', firstRow));

                foreach (var item in TimestampItems)
                {
                    writer.WriteLine(String.Join(',', [item.TimeOffset, item.SyncState, item.CpuTime, item.HostPosix, item.GuestPosix, item.GnssPosix]));
                }
            }

            await AgencyLocal.PopupNotice(language["file-exported"]);
        }

        [RelayCommand]
        private void Confirm()
        {
            CloseRequest?.Invoke(this, EventArgs.Empty);
        }

        public event EventHandler CloseRequest;
    }
}